﻿namespace Jsl.FxCop
{
    using System;
    using System.Diagnostics;

    /// <summary>
    /// Similar to the Maybe monad but less overhead because you don't have to create Maybe instances.
    /// </summary>
    /// <remarks>
    /// A functional way of testing objects for <c>null</c>.
    /// </remarks>
    public static class NullMonad
    {
        /// <summary>
        /// Does the action if <paramref name="value"/> not <c>null</c>.
        /// </summary>
        /// <typeparam name="TValue">The type of the value.</typeparam>
        /// <param name="value">The value to check for <c>null</c>.</param>
        /// <param name="action">The action to do if <paramref name="value"/> is not <c>null</c>.</param>
        /// <returns>The incoming value.</returns>
        [DebuggerStepThrough]
        public static TValue DoIfNotNull<TValue>(this TValue value, Action<TValue> action)
            where TValue : class
        {
            if (value != null)
            {
                action(value);
            }

            return value;
        }

        /// <summary>
        /// Does the action if <paramref name="values"/> and the two values inside it are not <c>null</c>.
        /// </summary>
        /// <typeparam name="TValue1">The type of the first value.</typeparam>
        /// <typeparam name="TValue2">The type of the second value.</typeparam>
        /// <param name="values">The values to check.</param>
        /// <param name="action">The action to do if both values are not null.</param>
        /// <returns>The incoming values.</returns>
        [DebuggerStepThrough]
        public static Tuple<TValue1, TValue2> DoIfNotNull<TValue1, TValue2>(
            this Tuple<TValue1, TValue2> values,
            Action<TValue1, TValue2> action)
            where TValue1 : class
            where TValue2 : class
        {
            if ((values != null)
                && (values.Item1 != null)
                && (values.Item2 != null))
            {
                action(values.Item1, values.Item2);
            }

            return values;
        }

        /// <summary>
        /// Does the action if the value is <c>null</c>.
        /// </summary>
        /// <typeparam name="TValue">The type of the value.</typeparam>
        /// <param name="value">The value to check.</param>
        /// <param name="action">The action to do if the value is <c>null</c>.</param>
        /// <returns>The incoming value.</returns>
        [DebuggerStepThrough]
        public static TValue DoIfNull<TValue>(this TValue value, Action action)
            where TValue : class
        {
            if (value == null)
            {
                action();
            }

            return value;
        }

        /// <summary>
        /// Does the action if the <paramref name="values"/> is <c>null</c> or both parts of the value are <c>null</c>.
        /// </summary>
        /// <typeparam name="TValue1">The type of the first value.</typeparam>
        /// <typeparam name="TValue2">The type of the second value.</typeparam>
        /// <param name="values">The values.</param>
        /// <param name="action">The action.</param>
        /// <returns>The incoming values.</returns>
        [DebuggerStepThrough]
        public static Tuple<TValue1, TValue2> DoIfNull<TValue1, TValue2>(this Tuple<TValue1, TValue2> values, Action action)
            where TValue1 : class
            where TValue2 : class
        {
            if ((values == null)
                || ((values.Item1 == null) && (values.Item2 == null)))
            {
                action();
            }

            return values;
        }

        /// <summary>
        /// Returns the incoming if value are not <c>null</c> and condition is <c>true</c>.
        /// </summary>
        /// <typeparam name="TValue">The type of the value.</typeparam>
        /// <param name="value">The value.</param>
        /// <param name="condition">The condition.</param>
        /// <returns>The incoming value if the value is not <c>null</c> and the condition is <c>true</c>.</returns>
        [DebuggerStepThrough]
        public static TValue If<TValue>(this TValue value, Func<TValue, bool> condition)
            where TValue : class
        {
            return (value != null) && condition(value) ? value : null;
        }

        /// <summary>
        /// Returns the incoming value is the value is not <c>null</c> and the condition is <c>false</c>.
        /// </summary>
        /// <typeparam name="TValue">The type of the value.</typeparam>
        /// <param name="value">The value.</param>
        /// <param name="condition">The condition.</param>
        /// <returns>The incoming value is the value is not <c>null</c> and the condition is <c>false</c>.</returns>
        [DebuggerStepThrough]
        public static TValue IfNot<TValue>(this TValue value, Func<TValue, bool> condition)
            where TValue : class
        {
            return (value != null) && !condition(value) ? value : null;
        }

        /// <summary>
        /// Returns the result of the <paramref name="expression"/> if the <paramref name="value"/> is not <c>null</c>.
        /// </summary>
        /// <typeparam name="TValue">The type of the value.</typeparam>
        /// <typeparam name="TResult">The type of the result.</typeparam>
        /// <param name="value">The value.</param>
        /// <param name="expression">The expression.</param>
        /// <returns>The result of the <paramref name="expression"/> if the <paramref name="value"/> is not <c>null</c>.</returns>
        [DebuggerStepThrough]
        public static TResult IfNotNull<TValue, TResult>(this TValue value, Func<TValue, TResult> expression)
            where TValue : class
            where TResult : class
        {
            return value == null ? null : expression(value);
        }

        /// <summary>
        /// Returns the results of the <paramref name="expression1"/> and <paramref name="expression2"/> if the
        /// <paramref name="value"/> is not <c>null</c>.
        /// </summary>
        /// <typeparam name="TValue">The type of the value.</typeparam>
        /// <typeparam name="TResult1">The type of the first result.</typeparam>
        /// <typeparam name="TResult2">The type of the second result.</typeparam>
        /// <param name="value">The value.</param>
        /// <param name="expression1">The first expression.</param>
        /// <param name="expression2">The second expression.</param>
        /// <returns>
        /// The results of the <paramref name="expression1"/> and <paramref name="expression2"/> if the
        /// <paramref name="value"/> is not <c>null</c>.
        /// </returns>
        [DebuggerStepThrough]
        public static Tuple<TResult1, TResult2> IfNotNull<TValue, TResult1, TResult2>(
            this TValue value,
            Func<TValue, TResult1> expression1,
            Func<TValue, TResult2> expression2)
            where TResult1 : class
            where TResult2 : class
        {
            if (value != null)
            {
                return new Tuple<TResult1, TResult2>(expression1(value), expression2(value));
            }

            return null;
        }

        /// <summary>
        /// Returns the <paramref name="newValue"/> if the <paramref name="value"/> is <c>null</c>.
        /// </summary>
        /// <typeparam name="TValue">The type of the value.</typeparam>
        /// <param name="value">The value.</param>
        /// <param name="newValue">The new value.</param>
        /// <returns>The <paramref name="newValue"/> if the <paramref name="value"/> is <c>null</c>.</returns>
        [DebuggerStepThrough]
        public static TValue IfNull<TValue>(this TValue value, TValue newValue)
            where TValue : class
        {
            return value == null ? newValue : value;
        }
    }
}